﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Net.Sockets;
using System.Net;
using System.Linq;
using System.Text;
using System.Threading;
using XSocketServerLib.Filter;
using XSocketServerLib.Config;
namespace XSocketServerLib
{
    public class XSocketServer
    {
        #region Private Variable

        private int port;
        private IPAddress ip;
        private bool is_start;
        private Socket socket_server;
        #endregion

        #region Static Variable
        public static int MaxQueue = 100;
        
        #endregion

        #region Property
        public FilterDecorator Filter { get; set; }
        public XServerConfig Configuration {get; set;}
        #endregion

        #region Internal
      
        internal Hashtable socket_manager = new Hashtable();
        #endregion

        public XSocketServer(int port,IPAddress ip)
        {
            this.Configuration = new XServerConfig();
            this.port = port;
            this.ip = ip;
            try
            {
                socket_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket_server.Bind(new IPEndPoint(ip, port));
            }
            catch (Exception ex)
            {
                Log2Txt.Insitance.Log(ex);
            }
        }
        public XSocketServer(int port, IPAddress ip, XServerConfig config)
        {
            this.Configuration = config;
        }
        public void Start()
        {
            is_start = true;
            socket_server.Listen(MaxQueue);
            ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptConnectThread),socket_server);
            //ThreadPool.QueueUserWorkItem(new WaitCallback(ScanForAvailable), socket_server);
        }
        public void ShutDown()
        {
            is_start = false;
        }
        private void AcceptConnectThread(object State)
        {
            Socket sock = State as Socket;
            Socket client;
            while (is_start)
            {
                try
                {
                    client = sock.Accept();
                    client.NoDelay = false;
                    if (Filter != null)
                    {
                        if (Filter.Check(client) == false)
                            continue;
                    }
                    IPEndPoint ipend = client.RemoteEndPoint as IPEndPoint;
                    Monitor.Enter(socket_manager);

                    if (Configuration.AllowMutipleConnection == false)
                    {
                        if (socket_manager.ContainsKey(ipend.Address + ":" + ipend.Port))
                            throw new Exception("Client Already Connect");
                    }
                    client.SendBufferSize = 512000;
                    client.ReceiveBufferSize = 512000;
                    socket_manager.Add(ipend.Address + ":" + ipend.Port, client);
                    ThreadPool.QueueUserWorkItem(new WaitCallback(ReciveMessage), ipend.Address + ":" + ipend.Port);
                    Console.WriteLine("Accept Connection form :" + ipend.Address + ":" + ipend.Port);
                    Monitor.Exit(socket_manager);
                }
                catch (Exception ex)
                {
                    Log2Txt.Insitance.Log(ex);
                    Monitor.Exit(socket_manager);
                }
            }
        }



        private void ScanForAvailable(object State)
        {
           
            while (is_start)
            {
                try
                {
                    Monitor.Enter(socket_manager);
                    foreach (string key in socket_manager.Keys)
                    {
                        Socket soc=socket_manager[key] as Socket;
                        if (soc.Send(new byte[] { 2, 0, 0, 0 }) != 4)
                        {
                            socket_manager.Remove(key);
                            Kill(soc, key, "Ping Error");
                            throw new Exception("Ping Error to " + key);

                        }
                            
                    }
                    Monitor.Exit(socket_manager);
                    Thread.Sleep(10000);
                }
                catch (Exception ex)
                {
                    
                    Log2Txt.Insitance.Log(ex);
                }
            }
        }
        private void ReciveMessage(object state)
        {

            try
            {
                Monitor.Enter(this.socket_manager);
                Socket sock = socket_manager[state] as Socket;
                Monitor.Exit(this.socket_manager);
                byte[] Buffer=new byte[1024];
                while (is_start)
                {
                    if (sock.Available != 0)
                    {
                        Buffer = new byte[sock.Available];
                    }
                    sock.Receive(Buffer);
                    if(this.XSocketRecive!=null)
                        this.XSocketRecive(this,new XSocketReciveEventArgs(Buffer,state.ToString()));
                    
                    Thread.Sleep(200);
                }
            }
            catch (Exception ex)
            {
                Log2Txt.Insitance.Log(ex);
            }
                
        }
    
        private void Kill(Socket client, string key, string reason)
        {
            client.Shutdown(SocketShutdown.Both);
            Console.WriteLine("Shut Down" + key + ": " + reason);
            Log2Txt.Insitance.Log("Shut Down" + key + ": " + reason);
        }

        #region Public
        public void Send(string Key, byte[] data)
        {
            Socket socket = this.socket_manager[Key] as Socket;
            
            socket.Send(data);
        }
        public void Send(string Key, string path)
        {
            Socket socket = this.socket_manager[Key] as Socket;
            socket.SendFile(path);
        }
        #endregion

        #region Event

        public event XSocketReciveHandler XSocketRecive;
        public delegate void XSocketReciveHandler(object sender, XSocketReciveEventArgs args);

        #endregion
    }
    public class XSocketReciveEventArgs : EventArgs
    {
        public byte[] Data { get; private set; }
        public string Name { get; private set; }
        

        public XSocketReciveEventArgs(byte[] data, string name)
        {
            this.Data = data;
            this.Name = name;
           
        }
    }
}
